#include "FanFloat.h"

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <fan-llvm/core/Core.h>
#include "CastErr.h"
#include "Decimal.h"
#include "ParseErr.h"
#include "Str.h"
#include "Type.h"
#include "UnsupportedErr.h"


static Float kNanFloat;
static Float kPosInfFloat;
static Float kNegInfFloat;
static Str* kNanStr;
static Str* kPosInfStr;
static Str* kNegInfStr;
static Str* kNanCode;
static Str* kPosInfCode;
static Str* kNegInfCode;

Type* Float_classType = NULL;
VTable* Float_classVTable = NULL;


/**********************************************
 *
 * Methods and fields
 *
 **********************************************/


/* Bool equals(Obj? obj) */
Bool Float_equals(Obj self, NullableObj obj)
{
  if (!VAL_IS_NULL(obj) && Obj_typeof(obj) == Float_classType)
    return (VAL_GET_FLOAT(self) == VAL_GET_FLOAT(obj));
  else
    return FALSE;
}


/* Int compare(Obj obj) */
Int Float_compare(Obj self, Obj obj)
{
  if (Obj_typeof(obj) == Float_classType)
  {
    Float f = VAL_GET_FLOAT(self);
    Float g = VAL_GET_FLOAT(obj);

    if (f < g)
      return -1;
    else if (f > g)
      return 1;
    else
      return 0;
  }
  else
  {
    exceptionThrow(CastErr_newWithTypes(Obj_typeof(obj), Float_classType));
    return 0;
  }
}


/* Int hash() */
Int Float_hash(Obj self)
{
#ifdef INT_IS_64BIT
  double floatVal = VAL_GET_FLOAT(self);
  int64_t* intPtr = (int64_t*)&floatVal;
  return *intPtr;
#else
  float floatVal = VAL_GET_FLOAT(self);
  int32_t* intPtr = (int32_t*)&floatVal;
  return *intPtr;
#endif
}


/* Str toStr() */
Str* Float_toStr(Obj self)
{
  Float f = VAL_GET_FLOAT(self);
  if (f == kNanFloat)
    return kNanStr;
  else if (f == kPosInfFloat)
    return kPosInfStr;
  else if (f == kNegInfFloat)
    return kNegInfStr;
  else
  {
    char buffer[32];
    snprintf(buffer, 32, "%.12g", f);
    return Str_newFromCStr(buffer);
  }
}


/* Float defVal */
Float Float_defVal(void)
{
  return 0.0;
}


/* Float posInf */
Float Float_posInf(void)
{
  return kPosInfFloat;
}


/* Float negInf */
Float Float_negInf(void)
{
  return kNegInfFloat;
}


/* Float nan */
Float Float_nan(void)
{
  return kNanFloat;
}


/* Float e */
Float Float_e(void)
{
  return 2.7182818284590452354;
}


/* Float pi */
Float Float_pi(void)
{
  return 3.14159265358979323846;
}


/* Float makeBits(Int bits) */
Float Float_makeBits(Int bits)
{
  int64_t intVal = bits;
  double* floatPtr = (double*)&intVal;
  return (Float)*floatPtr;
}


/* Float makeBits32(Int bits) */
Float Float_makeBits32(Int bits)
{
  int32_t intVal = bits;
  float* floatPtr = (float*)intVal;
  return (Float)*floatPtr;
}


/* Float? fromStr(Str s, Bool checked) */
NullableFloat Float_fromStr_1(Str* s)
{
  return Float_fromStr(s, TRUE);
}


/* Float? fromStr(Str s, Bool checked) */
NullableFloat Float_fromStr(Str* s, Bool checked)
{
  s = Str_trim(s);
  if (Str_equals(VAL_MAKE_REF(s), VAL_MAKE_REF(kNanStr)))
    return VAL_MAKE_FLOAT(kNanFloat);
  else if (Str_equals(VAL_MAKE_REF(s), VAL_MAKE_REF(kPosInfStr)))
    return VAL_MAKE_FLOAT(kPosInfFloat);
  else if (Str_equals(VAL_MAKE_REF(s), VAL_MAKE_REF(kNegInfStr)))
    return VAL_MAKE_FLOAT(kNegInfFloat);
  else
  {
    char buffer[32];
    if (Str_fillCStr(s, buffer, 32))
    {
      char* endPtr = NULL;
      double val = strtod(buffer, &endPtr);
      if (endPtr > buffer && *endPtr == '\0')
        return VAL_MAKE_FLOAT(val);
    }

    if (checked)
      exceptionThrow(ParseErr_newWithFormattedMsg("Invalid Float: '%r'", s));

    return VAL_NULL;
  }
}


/* Float random() */
Float Float_random(void)
{
  uint32_t randVal = ((rand() & 0xFF) << 16) | (rand() & 0xFF);
  return (Float)((double)randVal / ((double)UINT32_MAX + 1));
}


/* Bool approx(Float r, Float? tolerance) */
Bool Float_approx_1(Float self, Float r)
{
  return Float_approx(self, r, VAL_NULL);
}


/* Bool approx(Float r, Float? tolerance) */
Bool Float_approx(Float self, Float r, NullableFloat tolerance)
{
  if (self == r)
    return TRUE;

  double t;
  if (VAL_IS_NULL(tolerance))
  {
    double t1 = self / 1.0e6;
    double t2 = r / 1.0e6;
    t1 = (t1 >= 0.0) ? t1 : -t1;
    t2 = (t2 >= 0.0) ? t2 : -t2;
    t = (t1 <= t2) ? t1 : t2;
  }
  else
    t = VAL_GET_FLOAT(tolerance);

  Float diff = self - r;
  diff = (diff >= 0.0) ? diff : -diff;
  return (diff <= t);
}


/* Bool isNaN() */
Bool Float_isNaN(Float self)
{
  return (self != self);
}


/* Float negate() */
Float Float_negate(Float self)
{
  return -self;
}


/* Float increment() */
Float Float_increment(Float self)
{
  return self + 1.0;
}


/* Float decrement() */
Float Float_decrement(Float self)
{
  return self - 1.0;
}


/* Float mult(Float b) */
Float Float_mult(Float self, Float b)
{
  return self * b;
}


/* Float multInt(Int b) */
Float Float_multInt(Float self, Int b)
{
  return self * b;
}


/* Decimal multDecimal(Decimal b) */
Decimal* Float_multDecimal(Float self, Decimal* b)
{
  return Decimal_mult(Num_toDecimal(VAL_MAKE_FLOAT(self)), b);
}


/* Float div(Float b) */
Float Float_div(Float self, Float b)
{
  return self / b;
}


/* Float divInt(Int b) */
Float Float_divInt(Float self, Int b)
{
  return self / b;
}


/* Decimal divDecimal(Decimal b) */
Decimal* Float_divDecimal(Float self, Decimal* b)
{
  return Decimal_div(Num_toDecimal(VAL_MAKE_FLOAT(self)), b);
}


/* Float mod(Float b) */
Float Float_mod(Float self, Float b)
{
#ifdef FLOAT_IS_64BIT
  return fmod(self, b);
#else
  return fmodf(self, b);
#endif
}


/* Float modInt(Int b) */
Float Float_modInt(Float self, Int b)
{
#ifdef FLOAT_IS_64BIT
  return fmod(self, b);
#else
  return fmodf(self, b);
#endif
}


/* Decimal modDecimal(Decimal b) */
Decimal* Float_modDecimal(Float self, Decimal* b)
{
  return Decimal_mod(Num_toDecimal(VAL_MAKE_FLOAT(self)), b);
}


/* Float plus(Float b) */
Float Float_plus(Float self, Float b)
{
  return self + b;
}


/* Float plusInt(Int b) */
Float Float_plusInt(Float self, Int b)
{
  return self + b;
}


/* Decimal plusDecimal(Decimal b) */
Decimal* Float_plusDecimal(Float self, Decimal* b)
{
  return Decimal_plus(Num_toDecimal(VAL_MAKE_FLOAT(self)), b);
}


/* Float minus(Float b) */
Float Float_minus(Float self, Float b)
{
  return self - b;
}


/* Float minusInt(Int b) */
Float Float_minusInt(Float self, Int b)
{
  return self - b;
}


/* Decimal minusDecimal(Decimal b) */
Decimal* Float_minusDecimal(Float self, Decimal* b)
{
  return Decimal_minus(Num_toDecimal(VAL_MAKE_FLOAT(self)), b);
}


/* Float abs() */
Float Float_abs(Float self)
{
  return (self >= 0.0) ? self : -self;
}


/* Float min(Float that) */
Float Float_min(Float self, Float that)
{
  return (self <= that) ? self : that;
}


/* Float max(Float that) */
Float Float_max(Float self, Float that)
{
  return (self >= that) ? self : that;
}


/* Float ceil() */
Float Float_ceil(Float self)
{
#ifdef FLOAT_IS_64BIT
  return ceil(self);
#else
  return ceilf(self);
#endif
}


/* Float floor() */
Float Float_floor(Float self)
{
#ifdef FLOAT_IS_64BIT
  return floor(self);
#else
  return floorf(self);
#endif
}


/* Float round() */
Float Float_round(Float self)
{
#ifdef FLOAT_IS_64BIT
  return round(self);
#else
  return roundf(self);
#endif
}


/* Float exp() */
Float Float_exp(Float self)
{
#ifdef FLOAT_IS_64BIT
  return exp(self);
#else
  return expf(self);
#endif
}


/* Float log() */
Float Float_log(Float self)
{
#ifdef FLOAT_IS_64BIT
  return log(self);
#else
  return logf(self);
#endif
}


/* Float log10() */
Float Float_log10(Float self)
{
#ifdef FLOAT_IS_64BIT
  return log10(self);
#else
  return log10f(self);
#endif
}


/* Float pow(Float pow) */
Float Float_pow(Float self, Float pow)
{
#ifdef FLOAT_IS_64BIT
  return pow(self, pow);
#else
  return powf(self, pow);
#endif
}


/* Float sqrt() */
Float Float_sqrt(Float self)
{
#ifdef FLOAT_IS_64BIT
  return sqrt(self);
#else
  return sqrtf(self);
#endif
}


/* Float acos() */
Float Float_acos(Float self)
{
#ifdef FLOAT_IS_64BIT
  return acos(self);
#else
  return acosf(self);
#endif
}


/* Float asin() */
Float Float_asin(Float self)
{
#ifdef FLOAT_IS_64BIT
  return asin(self);
#else
  return asinf(self);
#endif
}


/* Float atan() */
Float Float_atan(Float self)
{
#ifdef FLOAT_IS_64BIT
  return atan(self);
#else
  return atanf(self);
#endif
}


/* Float atan2(Float y, Float x) */
Float Float_atan2(Float y, Float x)
{
#ifdef FLOAT_IS_64BIT
  return atan2(y, x);
#else
  return atan2f(y, x);
#endif
}


/* Float cos() */
Float Float_cos(Float self)
{
#ifdef FLOAT_IS_64BIT
  return cos(self);
#else
  return cosf(self);
#endif
}


/* Float cosh() */
Float Float_cosh(Float self)
{
#ifdef FLOAT_IS_64BIT
  return cosh(self);
#else
  return coshf(self);
#endif
}


/* Float sin() */
Float Float_sin(Float self)
{
#ifdef FLOAT_IS_64BIT
  return sin(self);
#else
  return sinf(self);
#endif
}


/* Float sinh() */
Float Float_sinh(Float self)
{
#ifdef FLOAT_IS_64BIT
  return sinh(self);
#else
  return sinhf(self);
#endif
}


/* Float tan() */
Float Float_tan(Float self)
{
#ifdef FLOAT_IS_64BIT
  return tan(self);
#else
  return tanf(self);
#endif
}


/* Float tanh() */
Float Float_tanh(Float self)
{
#ifdef FLOAT_IS_64BIT
  return tanh(self);
#else
  return tanhf(self);
#endif
}


/* Float toDegrees() */
Float Float_toDegrees(Float self)
{
  return self * 57.29577951308232087679;
}


/* Float toRadians() */
Float Float_toRadians(Float self)
{
  return self * 0.01745329251994329576;
}


/* Int bits() */
Int Float_bits(Float self)
{
  double floatVal = self;
  int64_t* intPtr = (int64_t*)&floatVal;
  return (Int)*intPtr;
}


/* Int bits32() */
Int Float_bits32(Float self)
{
  float floatVal = self;
  int32_t* intPtr = (int32_t*)&floatVal;
  return (Int)*intPtr;
}


/* Str toCode() */
Str* Float_toCode(Float self)
{
  if (self == kNanFloat)
    return kNanCode;
  else if (self == kPosInfFloat)
    return kPosInfCode;
  else if (self == kNegInfFloat)
    return kNegInfCode;
  else
  {
    char buffer[32];
    snprintf(buffer, 32, "%.12gf", self);
    return Str_newFromCStr(buffer);
  }
}


/* Str toLocale(Str? pattern) */
Str* Float_toLocale_0(Float self)
{
  return Float_toLocale(self, NULL);
}


/* Str toLocale(Str? pattern) */
Str* Float_toLocale(Float self, Str* pattern)
{
  exceptionThrow(UnsupportedErr_newWithFormattedMsg("sys::Float.toLocale not implemented yet"));
  return NULL;
}


/**********************************************
 *
 * Type-system functions
 *
 **********************************************/


Type* Float_initType(void)
{
  if (Float_classType == NULL)
  {
    Type* baseType = Num_initType();
    VTable* vtable = Float_initVTable();
    Type* type = Type_new("Float", baseType, vtable, sizeof(FanFloat), sizeof(FanFloatVTable));
    Type_registerMethodImplementation(type, "abs", Float_abs, 0);
    Type_registerMethodImplementation(type, "acos", Float_acos, 0);
    Type_registerMethodImplementation(type, "approx", Float_approx_1, 1);
    Type_registerMethodImplementation(type, "approx", Float_approx, 2);
    Type_registerMethodImplementation(type, "asin", Float_asin, 0);
    Type_registerMethodImplementation(type, "atan", Float_atan, 0);
    Type_registerMethodImplementation(type, "atan2", Float_atan2, 2);
    Type_registerMethodImplementation(type, "bits", Float_bits, 0);
    Type_registerMethodImplementation(type, "bits32", Float_bits32, 0);
    Type_registerMethodImplementation(type, "ceil", Float_ceil, 0);
    Type_registerMethodImplementation(type, "compare", Float_compare, 1);
    Type_registerMethodImplementation(type, "cos", Float_cos, 0);
    Type_registerMethodImplementation(type, "cosh", Float_cosh, 0);
    Type_registerMethodImplementation(type, "decrement", Float_decrement, 0);
    Type_registerMethodImplementation(type, "div", Float_div, 1);
    Type_registerMethodImplementation(type, "divDecimal", Float_divDecimal, 1);
    Type_registerMethodImplementation(type, "divInt", Float_divInt, 1);
    Type_registerMethodImplementation(type, "equals", Float_equals, 1);
    Type_registerMethodImplementation(type, "exp", Float_exp, 0);
    Type_registerMethodImplementation(type, "floor", Float_floor, 0);
    Type_registerMethodImplementation(type, "fromStr", Float_fromStr_1, 1);
    Type_registerMethodImplementation(type, "fromStr", Float_fromStr, 2);
    Type_registerMethodImplementation(type, "hash", Float_hash, 0);
    Type_registerMethodImplementation(type, "increment", Float_increment, 0);
    Type_registerMethodImplementation(type, "isNaN", Float_isNaN, 0);
    Type_registerMethodImplementation(type, "log", Float_log, 0);
    Type_registerMethodImplementation(type, "log10", Float_log10, 0);
    Type_registerMethodImplementation(type, "makeBits", Float_makeBits, 1);
    Type_registerMethodImplementation(type, "makeBits32", Float_makeBits32, 1);
    Type_registerMethodImplementation(type, "max", Float_max, 1);
    Type_registerMethodImplementation(type, "min", Float_min, 1);
    Type_registerMethodImplementation(type, "minus", Float_minus, 1);
    Type_registerMethodImplementation(type, "minusDecimal", Float_minusDecimal, 1);
    Type_registerMethodImplementation(type, "minusInt", Float_minusInt, 1);
    Type_registerMethodImplementation(type, "mod", Float_mod, 1);
    Type_registerMethodImplementation(type, "modDecimal", Float_modDecimal, 1);
    Type_registerMethodImplementation(type, "modInt", Float_modInt, 1);
    Type_registerMethodImplementation(type, "mult", Float_mult, 1);
    Type_registerMethodImplementation(type, "multDecimal", Float_multDecimal, 1);
    Type_registerMethodImplementation(type, "multInt", Float_multInt, 1);
    Type_registerMethodImplementation(type, "negate", Float_negate, 0);
    Type_registerMethodImplementation(type, "plus", Float_plus, 1);
    Type_registerMethodImplementation(type, "plusDecimal", Float_plusDecimal, 1);
    Type_registerMethodImplementation(type, "plusInt", Float_plusInt, 1);
    Type_registerMethodImplementation(type, "pow", Float_pow, 1);
    Type_registerMethodImplementation(type, "random", Float_random, 0);
    Type_registerMethodImplementation(type, "round", Float_round, 0);
    Type_registerMethodImplementation(type, "sin", Float_sin, 0);
    Type_registerMethodImplementation(type, "sinh", Float_sinh, 0);
    Type_registerMethodImplementation(type, "sqrt", Float_sqrt, 0);
    Type_registerMethodImplementation(type, "tan", Float_tan, 0);
    Type_registerMethodImplementation(type, "tanh", Float_tanh, 0);
    Type_registerMethodImplementation(type, "toCode", Float_toCode, 0);
    Type_registerMethodImplementation(type, "toDegrees", Float_toDegrees, 0);
    Type_registerMethodImplementation(type, "toLocale", Float_toLocale_0, 0);
    Type_registerMethodImplementation(type, "toLocale", Float_toLocale, 1);
    Type_registerMethodImplementation(type, "toRadians", Float_toRadians, 0);
    Type_registerMethodImplementation(type, "toStr", Float_toStr, 0);
    Type_registerFieldImplementation(type, "defVal", Float_defVal, NULL);
    Type_registerFieldImplementation(type, "e", Float_e, NULL);
    Type_registerFieldImplementation(type, "nan", Float_nan, NULL);
    Type_registerFieldImplementation(type, "negInf", Float_negInf, NULL);
    Type_registerFieldImplementation(type, "pi", Float_pi, NULL);
    Type_registerFieldImplementation(type, "posInf", Float_posInf, NULL);
    Float_classType = type;
    Float_initStatic();
  }
  return Float_classType;
}


VTable* Float_initVTable(void)
{
  if (Float_classVTable == NULL)
  {
    VTable* vtable = memoryAlloc(sizeof(FanFloatVTable));
    memcpy(vtable, Num_initVTable(), sizeof(FanNumVTable));
    ((FanObjVTable*)vtable)->compare = Float_compare;
    ((FanObjVTable*)vtable)->equals = Float_equals;
    ((FanObjVTable*)vtable)->hash = Float_hash;
    ((FanObjVTable*)vtable)->toStr = Float_toStr;
    Float_classVTable = vtable;
  }
  return Float_classVTable;
}


void Float_initStatic(void)
{
  kNanFloat = sqrt(-1.0);
  kPosInfFloat = 1.0 / 0.0;
  kNegInfFloat = -kPosInfFloat;

  kNanStr = Str_newFromCStr("NaN");
  kPosInfStr = Str_newFromCStr("INF");
  kNegInfStr = Str_newFromCStr("-INF");

  kNanCode = Str_newFromCStr("Float.nan");
  kPosInfCode = Str_newFromCStr("Float.posInf");
  kNegInfCode = Str_newFromCStr("Float.negInf");

  srand(time(NULL));
}
